﻿using System;
using System.Collections.Generic;
using SLNGP.Common.Extensions;
using SLNGP.Core.Run;
using System.Linq;
using SLNGP.Core.SecondaryStrategies;

namespace SLNGP.Core.Genes
{
    public class ScalarFunctionGene<T> : Gene
    {
        private Delegate _scalarFunction;
        private string _symbol; 

        public ScalarFunctionGene(Delegate scalarFunction, string symbol)
        {
            _scalarFunction = scalarFunction;
            _symbol = symbol;
        }

        public override object Execute(IGeneticProgrammingRun run)
        {
            object[] parameters = Children.Select(gene => gene.Execute(run)).ToArray();

            if (parameters.Length != _scalarFunction.Method.GetParameters().Length)
                throw new GeneticProgrammingException("The numbers of Gene parameters does not match the supplied parameters");

            for (int i = 0; i < parameters.Length; i++)
            {
                Type curParameterType = parameters[i].GetType();
                Type expectedParameterType = _scalarFunction.Method.GetParameters()[i].ParameterType;
                if (!parameters[i].Is(expectedParameterType))
                {
                    throw new GeneticProgrammingException(string.Format("Parameter type does not match expected type: {0}, {1}", curParameterType.FullName, expectedParameterType.FullName));
                }
            }

            return _scalarFunction.DynamicInvoke(parameters).As<T>();
        }

        public override string GetStringDisplay()
        {
            return StringHelper.Format(_symbol, Children.Select(gene => gene.GetStringDisplay()).ToArray());
        }

        public override bool IsValid
        {
            get
            {
                var methodParameters = _scalarFunction.Method.GetParameters();
                for (int i = 0; i < methodParameters.Length; i++)
                {
                    // in case of missing children
                    if (Children.Count <= i)
                        return false;
                    // in case of type mismatch
                    if (Children[i].GeneReturnType != methodParameters[i].ParameterType)
                        return false;
                }
                return true;
            }
        }

        public override void Fix()
        {
            var methodParameters = _scalarFunction.Method.GetParameters();
            for (int i = 0; i < methodParameters.Length; i++)
            {
                // add new child
                if (Children.Count <= i)
                {
                    Children.Add(ParentGeneticProgram.GetStrategy<ISelectRandomGeneDefinition>()
                                                      .SelectRandomDefinition()
                                                      .Create());
                }
                // fix existing child
                while (Children[i].GeneReturnType != methodParameters[i].ParameterType)
                {
                    Children[i] = ParentGeneticProgram.GetStrategy<ISelectRandomGeneDefinition>()
                                                      .SelectRandomDefinition()
                                                      .Create();
                }
                // fix the child itself
                if (!Children[i].IsValid)
                {
                    Children[i].Fix();
                }
            }
        }

        public override Type GeneReturnType
        {
            get { return _scalarFunction.Method.ReturnType; }
        }
    }
}